/* kernel/mempool.c 
 * 
 * Copyright (C) 2021, 2022 intirain. 
 *
 * This file is part of evx. 
 * 
 * evx is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * evx is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with evx. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <kernel/init.h>
#include <kernel/module.h>
#include <kernel/mm.h>
#include <kernel/kernel.h>
#include <kernel/kmlog.h>

#include <sys/types.h>


/* early memory pools are for klist nodes because most early_free cannot 
 * release memory.
 *
 * of course you can use them for some other structures such as struct task 
 * etc. */

static struct mempool *mempool_create(int size, void *(*) (int), void *(*) (int));

static void *__early_alloc(int size) {
	return early_kmalloc(size, 1);
}

static void *__pool_alloc(int size) {
	return early_kmalloc(size, CACHE_SIZE);
}

struct mempool_dir mempools[] = {
	/* you can join a structure in here too */

	{ 16, NULL }, 
	{ 32, NULL }, 
	{ 64, NULL }, 
	{ 128, NULL }, 
	{ 256, NULL }, 
	{ 512, NULL }, 
	{ 1024, NULL }, 
	{ 2048, NULL }, 
	{ 4096, NULL }, 

	{ 0, NULL }
};

int early_mempool_init(void) {
	struct mempool_dir *dir;

	for (dir = mempools; dir->size; dir++) 
		dir->pools = mempool_create(dir->size, __early_alloc, __pool_alloc);

	return 0;
}

struct mempool *mempool_create(int size, void *(* alloc) (int size), void *(* pool_alloc) (int)) {
	struct mempool *pool;
	struct bucket_entry *e;
	int i;

	/* we don't support multiple-pools now */

	pool = alloc(sizeof(struct mempool));

	if (!pool) 
		/* this printk is not safe, as you know */
		panic("mempool_create: Out of memory\n");

	pool->size = size;

	/* we'll not free this */
	pool->cache = pool_alloc(CACHE_SIZE);

	if (!pool->cache) 
		panic("mempool_create: Out of memory\n");

	e = pool->freeptr = (struct bucket_entry *) pool->cache;

	for (i = 0; i < CACHE_SIZE / size; i++) {
		e->next = (struct bucket_entry *) ((char *) e + size);

		++e;
	}

	pool->next_pool = NULL;
	pool->used = 0;

	return pool;
}

void *__mempool_alloc(struct mempool *pool) {
	struct bucket_entry *e = pool->freeptr;
	struct mempool *p = pool;

	if (!e) {
		kmlog(KERN_INFO "__mempool_alloc: creating new pool ...\n");

		pool->next_pool = p = mempool_create(p->size, __early_alloc, __pool_alloc);
	}

	p->freeptr = e->next;
	p->used++;
	
	return (void *) e; 
}

int __mempool_free(struct mempool *pool, void *p) {
	struct bucket_entry *old, *e;

	e = (struct bucket_entry *) p;
	old = pool->freeptr;

	pool->freeptr = e;
	e->next = old;

	pool->used--;

	return 0;
}

void *kmalloc(int size) {
	struct mempool_dir *d;

	for (d = mempools; d->size; d++) 
		if (d->size >= size) 
			break;
	
	if (!d->size) 
		panic("kmalloc: size is too big\n");

	return __mempool_alloc(d->pools);
}

int free_s(void *p, int size) {
	struct mempool_dir *d;
	struct mempool *pool;

	void *cache = (void *) ((__archptr_t) p & ~CACHE_SIZE);

	for (d = mempools; d->size; d++) {
		if (d->size < size) 
			continue;

		pool = d->pools;

		while (pool && pool->cache != cache) 
			pool = pool->next_pool;

		if (!pool && size) 
			panic("free_s: object is not in any mempool\n");
	}

	if (!d->size) 
		panic("free_s: object is not in any mempool\n");

	return __mempool_free(pool, p);
}

int dump_pool(struct mempool_dir *d) {
	struct mempool *p;
	int total;

	p = d->pools;
	total = CACHE_SIZE / p->size;

	kmlog(KERN_INFO "dump_pool(%d): \t%d / %d: %d\n", d->size, p->used, total, p->used / total);

	return 0;
}

int kmalloc_report(void) {
	struct mempool_dir 	*d;

	for (d = mempools; d->size; d++) 
		dump_pool(d);

	return 0;
}

